#! /usr/bin/python
# -*- coding: utf-8 -*-
# @author HanYZ
# @createAt 2022.07.17
# 网易萧先生-亢龙有悔

import backtrader as bt

class StrategyClass(bt.Strategy):
    '''亢龙有悔, 二值版(twovalue = True), 连续版(twovalue = False)'''
    params = dict(
        bigvsmall_max = 7,    # 大小阴线最大柱比（高于这个比例就视同这个比例，避免异常高时影响判断）仅当twovalue = False有效
        weight = [1,1,1,1,1],  # 五个扩展指标的权重
        limit4buy = 0,        # 五项指标加权求和后高于此值买入
        period_big = 15,      # 大柱的观察期
        bigvmean = 2,         # 高于前观察期内最大柱，或大柱高于前观察期内均值的bigvmean倍
        fallenough = False,   # 老师所讲之外的扩展要求，大阴线足够大
        fallvmean = 1,        # 大阴线要大于观察期内均值的fallvmean倍，仅当fallenough=True时生效
        twovalue = True,      # 用二值法度量五项指标
        bigvsmall = 2,       # 柱体大小比, 仅当二值法有效
        shadowvbody = 2,      # 上影柱体比, 仅当二值法有效
        code = None,
        name = None,
        log = True,
    )

    def log(self, txt, dt=None, force=False):
        if force or self.p.log:
            dt = dt or self.data.datetime.datetime()
            who = f'{self.p.code} {self.p.name} ' if self.p.code else ''
            print(f'{dt.isoformat()} {who}{txt}')

    def notify_order(self, order):
        if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
            return

        if order.status == order.Completed:
            if order.isbuy():
                buytxt = '买入价:%.2f, 量:%s, 持仓:%s' % (order.executed.price, order.executed.size, self.position.size)
                self.log(buytxt, bt.num2date(order.executed.dt))
            else:
                selltxt = '卖出价, %.2f, 量:%s, 持仓:%s' % (
                    order.executed.price, order.executed.size, self.position.size)
                if 'log' in order.info:
                    selltxt = '%s %s' % (selltxt, order.info.log)
                self.log(selltxt, bt.num2date(order.executed.dt))

        elif order.status in [order.Expired, order.Canceled, order.Margin]:
            if 'log' not in order.info:
                self.log('%s , %s' % (order.Status[order.status], order))
            pass

    def __init__(self):
        rise = self.data.close > self.data.open
        fall = self.data.close < self.data.open

        body_high = bt.If(rise, self.data.close, self.data.open)
        body_low = bt.If(fall, self.data.close, self.data.open)
        body = body_high - body_low

        high_shadow = self.data.high - body_high
        low_shadow = body_low - self.data.low
        full = self.data.high - self.data.low
        short_high = full * 0.667 > high_shadow

        body_gt_pre = body > body(-1)
        open_lt_pre_close = self.data.open < self.data.close(-1)
        close_gt_pre_close = self.data.close > self.data.close(-1)
        high_lt_pre_high = self.data.high < self.data.high(-1)
        jump_low = self.data.high < self.data.low(-1)

        gap_oltc = (self.data.close(-1) - self.data.open) / (self.data.close(-1) + 0.001)

        body_mean = bt.ind.SMA(body, period=self.p.period_big)
        body_highest = bt.ind.Highest(body, period=self.p.period_big)

        # 大柱
        self.bigbody = bt.Or(body > body_highest(-1), body > body_mean(-1) * self.p.bigvmean)
        # 最低价
        self.stopprice = bt.ind.Lowest(self.data.low, period=3)

        # 基本形态，阴阴阳、阴有大小、阳包大阴底
        base = bt.And(fall(-2), fall(-1), rise(0), body_gt_pre(-1), open_lt_pre_close, close_gt_pre_close)
        self.original_base = base
        if self.p.fallenough:
            self.base = bt.And(body(-1) > body_mean(-2) * self.p.fallvmean, base)
        else:
            self.base = base

        if self.p.twovalue:
            self.r01 = bt.If(high_lt_pre_high, 1, 0)
            self.r02 = bt.If(body(-1) < body(-2) * self.p.bigvsmall, 1, 0)
            self.r03 = bt.If(jump_low(-1), 1, 0)
            self.r04 = bt.If(body * self.p.bigvsmall < body(-1), 1, 0)
            self.r05 = bt.If(bt.And(short_high(-2), short_high(-1), short_high), 1, 0)
        else:
            # 1、小阴线最好不被大阴线包含
            self.r01 = bt.If(
                self.data.open(-2) > self.data.high(-1), 0,
                bt.If(
                    self.data.open(-2) > self.data.open(-1),
                    bt.If(self.data.high(-2) > self.data.high(-1), -1, -2),
                    bt.If(
                        self.data.high(-2) > self.data.high(-1), -3,
                        bt.If(self.data.high(-2) > self.data.open(-2), -4, -5)
                    )
                )
            )

            # 2、小阴线与大阴线对比越明显越好
            r02 = body(-1) / (body(-2) + 0.001)
            self.r02 = bt.If(r02 > self.p.bigvsmall_max, self.p.bigvsmall_max, r02) / 2

            # 3、大阴线最好跳空低开
            self.r03 = bt.If(
                self.data.open(-1) <= self.data.close(-2), 0,
                bt.If(
                    bt.And(self.data.open(-1) > self.data.low(-2), self.data.high(-1) > self.data.close(-2)),
                    gap_oltc(-1) * 4,
                    bt.If(
                        bt.Or(self.data.open(-1) > self.data.low(-2),
                              self.data.high(-1) > self.data.close(-2)), 1 + gap_oltc(-1) * 3,
                        bt.If(self.data.high(-1) > self.data.low(-2), 3 + gap_oltc(-1) * 2, 5 + gap_oltc(-1))
                    )
                )
            )

            # 4、阳线实体最好偏小
            r04 = (body(-1) + body) / (body + 0.001)
            self.r04 = bt.If(
                body_gt_pre, (body(-1) - body) / (body + 0.001) ,
                bt.If(r04 > self.p.bigvsmall_max, self.p.bigvsmall_max, r04) - 2
            )

            # 5、全部3根K线的上影线不能太长
            r05 = (self.data.high - self.data.low + body) / (high_shadow + 0.001)
            self.r05 = bt.If(r05 > self.p.bigvsmall_max, self.p.bigvsmall_max, r05) - 3

        w = self.p.weight
        self.score = self.r01 * w[0] + self.r02 * w[1] + self.r03 * w[2] + self.r04 * w[3] + self.r05 * w[4]
        signal_buy = bt.And(self.base, self.score > self.p.limit4buy)
        signal_sell = bt.Or(bt.And(self.bigbody, rise), fall)
        self.signal = bt.If(signal_buy, 1, bt.If(signal_sell, -1, 0))

        self.waitfor = None
        self.drop = []

    def next(self):
        if self.waitfor:
            stop = self.waitfor[1]
            if stop.status in [bt.Order.Submitted, bt.Order.Accepted]:
                if len(self) == self.waitfor[0]:
                    self.sell(exectype=bt.Order.Close, log='十日结单', oco=stop)
                elif self.signal[0] < 0:
                    log = '阴线止盈' if self.data.open[0] > self.data.close[0] else '大阳止盈'
                    self.sell(exectype=bt.Order.Market, log=log, oco=self.waitfor[1])
                else:
                    return
            self.waitfor = None
        elif self.original_base[0]:
            txt = (f'阳包阴底 阳O{self.data.open[0]} 阴C{self.data.close[-0]} 阳C{self.data.close[0]}')
            self.log(txt)
            txt = (f'分项评估:[{self.r01[0]:.2f}, {self.r02[0]:.2f}, {self.r03[0]:.2f}' +
                   f', {self.r04[0]:.2f}, {self.r05[0]:.2f}], 总分:{self.score[0]:.2f}')
            self.log(txt)
            if self.base[0]:
                if self.signal[0] > 0:
                    self.buy()
                    stop = self.sell(exectype=bt.Order.Stop, price=self.stopprice, log=' 初始止损')
                    self.waitfor = [len(self)+9, stop]
                else:
                    self.log('五项评分不足，放弃')
                    self.drop.append([len(self)+1, len(self)+10, self.stopprice, 0, 0])
            else:
                self.log('大阴柱不够大，放弃 ori: %s, base: %s,  eb: %s' %
                         (self.original_base[0], self.base[0], self.p.fallenough))
                self.drop.append([len(self)+1, len(self)+10, self.stopprice, 0, 0])
        elif len(self.drop) > 0:
            for d in self.drop:
                if d[0] == len(self):
                    d[3] = d[4] = self.data.open[0]
                if d[1] == len(self):
                    if d[4] > d[3]:
                        self.log(f'上次放弃，让你少赚 %.2f' % (self.data.close[0]- d[3]) )
                    else:
                        self.log(f'上次放弃，避免损失 %.2f' % (d[3] - d[2]) )
                    self.drop.remove(d)
                else:
                    if self.data.low[0] < d[4]:
                        d[4] = self.data.low[0]

EasyNetRally01 = StrategyClass
